package com.util_code.retrofit;

import java.util.Date;
import java.util.List;
import java.util.Map;

import io.reactivex.Observable;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Part;
import retrofit2.http.PartMap;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
import retrofit2.http.Streaming;
import retrofit2.http.Url;

public interface RetrofitApi {
    /*Request method and URL specified in the annotation.
    Define the return value with a parameterized Call<T>.

    @Path variable substitution for the API endpoint.

    @Query specifies the query key name with the value of the annotated parameter.

    @Body payload for the POST call (serialized from a Java object to a JSON string).

    @Header specifies the header with the value of the annotated parameter.

    The return value is always a parameterized Call<T> object. If you do not need any
    type-specific response, you can specify return value as simply
    Call<ResponseBody>

    Retrofit provides two options to define HTTP request header fields: static and
    dynamic. Static headers can’t be changed for different requests. The header’s key
    and value are fixed and initiated with the app startup.

    A more customizable approach are dynamic headers. A dynamic header is passed like
    a parameter to the method. The provided parameter value gets mapped by Retrofit
    before executing the request.

    Retrofit will download and parse the API data on a background thread, and
    then deliver the results back to the UI thread via the onResponse or onFailure method.

    Note also that OkHttp, which dispatches the callback on the worker thread,
    but callbacks in Retrofit are dispatched on the main thread.

    If you are using Retrofit in a Background Service instead of an Activity or
    Fragment, you can run the network call synchronously within the same thread by
    using the execute() method.

    try {
        Response<User> response = call.execute();
    } catch (IOException e ){
        // handle error
    }*/

    @Headers({"Cache-Control: max-age=640000", "User-Agent: My-App-Name"})
    @GET("some/endpoint/users/{username}")
    Call<UserInfo> getUser(@Path("username") String username);

    @GET("some/endpoint/group/{id}/users")
    Call<List<UserInfo>> groupUserList(@Header("Content-Range") String contentRange, @Path("id") int groupId, @Query("sort") String sort);

    @GET("some/endpoint")
    Call<ResponseBody> getTask(@Query("id") long taskId);

    @GET("some/endpoint")
    Call<ResponseBody> getTask(@Query("id") List<Long> taskIds);
    /*id=id1&id=id2&id=id3&id=id4*/

    @GET("some/endpoint")
    Call<List<ResponseBody>> getNews(@Query("page") int page, @Query("order") String order, @Query("author") String author, @Query("published_at") Date date);

    @GET("news")
    Call<List<ResponseBody>> getNews(@QueryMap Map<String, String> options);

    @FormUrlEncoded
    @POST("tasks")
    Call<ResponseBody> createTask(@Field("title") String title);

    @FormUrlEncoded
    @POST("tasks")
    Call<List<ResponseBody>> createTasks(@Field("title") List<String> titles);
    /*title=Research+Retrofit&title=Retrofit+Form+Encoded*/

    @FormUrlEncoded
    @PUT("some/endpoint")
    Call<ResponseBody> update(@Field("username") String username, @Field("name") String name, @Field("email") String email, @Field("homepage") String homepage, @Field("location") String location);

    @FormUrlEncoded
    @PUT("some/endpoint")
    Call<ResponseBody> update(@FieldMap Map<String, String> fields);

    /*Retrofit 2 will use the converter library chosen to handle the
     deserialization of data from a Java object. If you annotate the
     parameter witha @Body parameter, this work will be done automatically.*/

    @POST("/some/endpoint/users/new")
    Call<UserInfo> createUser(@Body UserInfo user);

    /*{@link Part} supports three types {@ link RequestBody}, {@ link okhttp3.MultipartBody.Part}, any other types.
     Except {@link okhttp3.MultipartBody.Part}, the other types must take the form fields,
     ({@link okhttp3.MultipartBody.Part} already contains information form fields).*/

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFile(@Part("description") RequestBody description, @Part MultipartBody.Part file);

    // new code for multiple files
    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadMultipleFiles(@Part("description") RequestBody description, @Part MultipartBody.Part file1, @Part MultipartBody.Part file2);

    /*PartMap annotation support a Map as an argument in support of{@link RequestBody}
    types,if there are other types, would be {@link retrofit2.Converter} conversion,
    so {@link MultipartBody.Part} Does not apply, so the document can only use
    @Part MultipartBody.Part*/

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadFileWithPartMap(@PartMap() Map<String, RequestBody> partMap, @Part MultipartBody.Part file);

    /*
    public static final String MULTIPART_FORM_DATA = "multipart/form-data";

    @NonNull
    private RequestBody createPartFromString(String descriptionString) {
        return RequestBody.create(
                MediaType.parse(MULTIPART_FORM_DATA), descriptionString);
    }

    @NonNull
    private MultipartBody.Part prepareFilePart(String partName, Uri fileUri) {

        // use the FileUtils to get the actual file by uri
        File file = FileUtils.getFile(this, fileUri);

        // create RequestBody instance from file
        RequestBody requestFile =
                RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file);

        // MultipartBody.Part is used to send also the actual file name
        return MultipartBody.Part.createFormData(partName, file.getName(), requestFile);
    }

    Uri file1Uri = ... // get it from a file chooser or a camera intent
    Uri file2Uri = ... // get it from a file chooser or a camera intent

    // create upload service client
    FileUploadService service =
        ServiceGenerator.createService(FileUploadService.class);

    // create part for file (photo, video, ...)
    MultipartBody.Part body1 = prepareFilePart("video", file1Uri);
    MultipartBody.Part body2 = prepareFilePart("thumbnail", file2Uri);

    // add another part within the multipart request
    RequestBody description = createPartFromString("hello, this is description speaking");

    // finally, execute the request
    Call<ResponseBody> call = service.uploadMultipleFiles(description, body1, body2);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call,
            Response<ResponseBody> response) {
            Log.v("Upload", "success");
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.e("Upload error:", t.getMessage());
        }
    });

    Uri fileUri = ... // from a file chooser or a camera intent

    // create upload service client
    FileUploadService service =
        ServiceGenerator.createService(FileUploadService.class);

    // create part for file (photo, video, ...)
    MultipartBody.Part body = prepareFilePart("photo", fileUri);

    // create a map of data to pass along
    RequestBody description = createPartFromString("hello, this is description speaking");
    RequestBody place = createPartFromString("Magdeburg");
    RequestBody time = createPartFromString("2016");

    HashMap<String, RequestBody> map = new HashMap<>();
    map.put("description", description);
    map.put("place", place);
    map.put("time", time);

    // finally, execute the request
    Call<ResponseBody> call = service.uploadFileWithPartMap(map, body);
    call.enqueue(...);
    */

    /*How to Use Dynamic Urls
    Actually, it only requires you to add a single String parameter annotated with @Url
    in your endpoint definition. Short code says more than thousand words :)
    Url support type: okhttp3.HttpUrl, String, java.net.URI, android.net.Uri
    As Full URL "https://s3.amazon.com/profile-picture/path"
    As Partial URL as "profile-picture/path"
    */
    @GET
    public Call<ResponseBody> profilePicture(@Url String url);

    /*Normally, the base URL is defined when you instantiated an Retrofit instance.
    Retrofit 2 allows you to override the base URL specified by changing it in the
    annotation (i.e. if you need to keep one specific endpoint using an older API
    endpoint)*/

    @POST("https://api.github.com/api/v3")
    Call<ResponseBody> createUrlTest(@Body UserInfo user);

    /*the request declaration for downloading files looks almost like any other request
    Please note, that we're specifying ResponseBody as return type. You should not use
    anything else here, otherwise Retrofit will try to parse and convert it, which
    doesn't make sense when you're downloading a file.
    */
    // option 1: a resource relative to your base URL
    @GET("/resource/example.zip")
    Call<ResponseBody> downloadFileWithFixedUrl();

    // option 2: using a dynamic URL
    @GET
    Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);

    /*by default, Retrofit puts the entire server response into memory before
    processing the result. This works fine for some JSON or XML responses, but large
    files can easily cause Out-of-Memory-Errors.*/

    // Beware with Large Files: Use @Streaming!

    /*The @Streaming It means that instead of moving the entire file into memory, it'll
    pass along the bytes right away. But be careful, if adding the @Streaming
    declaration and continue to use the code above,
    Android will trigger a android.os.NetworkOnMainThreadException.
    */

    @Streaming
    @GET
    Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);

    /*Retrofit 2.0 comes with new URL resolving concept. Base URL and @Url have not
    just simply been combined together but have been resolved the same way as what
    <a href="..."> does instead.
    - Base URL: always ends with /
    - @Url: DO NOT start with /

    ServerUrl =  http://api.nuuneoi.com/base/user/level1
    @POST("list")
    http://api.nuuneoi.com/base/user/list

    ServerUrl =  http://api.nuuneoi.com/base/user/level1
    @POST("/list")
    http://api.nuuneoi.com/list

    ServerUrl =  http://api.nuuneoi.com/base/user/level1/
    @POST("list")
    http://api.nuuneoi.com/base/user/level1/list
    */

    /*HTTP注解则可以代替以上方法中的任意一个注解,有3个属性：method、path,hasBody
    method 表示请的方法，不区分大小写 path表示路径 hasBody表示是否有请求体*/

    /*Instead of creating Call objects, we will use Observable types.*/
    @HTTP(method = "get", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getFirstBlog(@Path("id") int id);


    //Rxjava2 + retrofit
    @GET("/users/{username}")
    Observable<UserInfo> getRxUser(@Path("username") String username);

    @GET("/group/{id}/users")
    Observable<List<UserInfo>> groupRxUserList(@Path("id") int groupId, @Query("sort") String sort);

    @POST("/users/new")
    Observable<UserInfo> createRxUser(@Body UserInfo user);

    /*The ResponseBody class allows us to receive any response data.*/
    @POST("path")
    Call<ResponseBody> getStringRequestBody(@Body RequestBody body);

    /*
    String text = "plain text request body";
    RequestBody body =
            RequestBody.create(MediaType.parse("text/plain"), text);
    Call<ResponseBody> call = service.getStringRequestBody(body);
    Response<ResponseBody> response = call.execute();
    String value = response.body().string();
    */

    public class UserInfo {
        private int id;

        private String name;

        private String avatarUrl;

        public UserInfo(int id, String name, String avatarUrl) {
            this.id = id;
            this.name = name;
            this.avatarUrl = avatarUrl;
        }
    }

    /*
    In Retrofit 2, all requests that can be executed (sent to the API) and for which
    you’re receiving a response are seen as "successful". That means, for these
    requests the onResponse callback is fired and you need to manually check whether
    the request is actual successful (status 200-299) or erroneous (status 400-599).

    // Retrofit
    Call<User> call = service.me();
    call.enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            if (response.isSuccessful()) {
                // use response data and do some fancy stuff :)
            } else {
                //For getting error message
                Log.d("Error message",response.message());
                //For getting error code. Code is integer value like 200,404 etc
                Log.d("Error code",String.valueOf(response.code()));
            }
        }

        @Override
        public void onFailure(Call<User> call, Throwable t) {
            // there is more than just a failing request (like: no internet connection)
        }
    });

    // Retrofit&Rxjava
    public void onError(Throwable throwable) {
        if (throwable instanceof HttpException) {
           // We had non-2XX http error
        }
        if (throwable instanceof IOException) {
           // A network or conversion error happened
        }

        // We don't know what happened. We need to simply convert to an unknown error
        // ...
    }
    */
}
